#!/usr/bin/python2.5
"""
    Googbus - Engine with DBUS interface for accessing Google APIs
    
    Copyright (C) 2008  Matti Raty

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/.
"""

import xml.utils.iso8601
import gdata.calendar.service
import atom
import gdata.calendar
import logging
import datetime
import time 
import string
import googuser
import googdefs

class GoogCalendar():
    """
    Googbus Calendar
    
    Contains methtods for accessing Google calendar
    """

    GOOGLE_CALENDAR_SERVICE_SOURCE  = "Googbus service - calendar - 0.1" 
    LOG_PREFIX                      = "GoogCalendar: "
    
    def __init__(self):
        logging.debug(GoogCalendar.LOG_PREFIX + "Class init started..")
        self.gcal_service = gdata.calendar.service.CalendarService()
        self.gcal_service.source = GoogCalendar.GOOGLE_CALENDAR_SERVICE_SOURCE
        
    def Login(self):
        """
        Logs in to Google Calendar service.
        
        Username and password are read from GoogUser class.
        """
        ret = googdefs.GOOGBUS_RET_OK
        
        user = googuser.GoogleUser()
        if user.read_user_data():
            self.gcal_service.email = user.username
            self.gcal_service.password = user.password
                
            try:
                self.gcal_service.ProgrammaticLogin()
                logging.debug(GoogCalendar.LOG_PREFIX + "Logged into service")
            except:
                logging.debug(GoogCalendar.LOG_PREFIX + "Invalid authentication!")
                ret = googdefs.GOOGBUS_RET_LOGIN_FAILED
        else:
            ret = googdefs.GOOGBUS_RET_NO_ACCOUNT
            logging.critical(GoogCalendar.LOG_PREFIX + "Unable to read user data!")
        
        return ret
            
            
    def ListAllCalendars(self):
        """
        Lists all user's calendars.
        
        Returns a tuple containing names of all calendars
        """
        
        feed = self.gcal_service.GetAllCalendarsFeed()
        logging.debug(GoogCalendar.LOG_PREFIX + "Got calendar feed - " + feed.title.text)
        
        calendars = []
        for i, calendar in enumerate(feed.entry):
            logging.debug(GoogCalendar.LOG_PREFIX + "\t%s. %s" % (i, calendar.title.text))
            calendars.append(calendar.title.text)
        
        return tuple(calendars)
    
    
    def ListEntries(self, start_time=None, end_time=None):
        """
        Lists entries on user's calendar.
        
        Parameters: 
        start_time = From where to list from (inclusive, optional)
        end_time = To where list to (exclusive, optional)
        
        Both of the parameters must be given if time range is wanted,
        otherwise method will return events in range specified by
        server.
        
        Returns tuple containing calendar entries. Each entry has own tuple.
            ((event0_title, start_time, end_time), (event1_title, start_time, end_time))
        
        Currently supports only primary (default) calendar.
        """
        ret = []
        
        feed = self.gcal_service.GetCalendarEventFeed()
        logging.debug(GoogCalendar.LOG_PREFIX + "Got event feed - " + feed.title.text)
        
        for i, event in enumerate(feed.entry):            
            event_info = []
            
            if event.title.text == None:
                event_info.append("") #title
            else:
                event_info.append(event.title.text) #title    
            
            if event.when:    
                event_when = self._parse_event_time(str(event.when[0]))
            
            if len(event_when) > 0:
                event_info.append(event_when[0]) #start time
                event_info.append(event_when[1]) #end time
            else:
                logging.debug(GoogCalendar.LOG_PREFIX + "No time information in event")
                event_info.append(None) #start time
                event_info.append(None) #end time
            
            logging.debug(GoogCalendar.LOG_PREFIX + "Event info: " + str(event_info))
            ret.append(tuple(event_info))
        
        return ret
    
    
    def ListEntriesFromNow(self):
        """
        Lists entries from current day on wards.
        Amount of entries returned depends on Google server.
        
        Returns tuple containing calendar entries. Each entry has own tuple.
            ((event0_title, start_time, end_time), (event1_title, start_time, end_time))
        Also returns count of events returned.
            
        """
        ret = []
        
        query = gdata.calendar.service.CalendarEventQuery("default", "private", "full")
        query.futureevents = "true"
        query.orderby = "starttime"
        query.sortorder = "ascending"
        query.singleevents = "true"
        
        feed = self.gcal_service.CalendarQuery(query)
        
        for i, event in enumerate(feed.entry):  
            event_info = []
            
            if event.title.text == None:
                event_info.append("") #title
            else:
                event_info.append(event.title.text) #title        
                
            event_when = self._parse_event_time(str(event.when[0]))
            
            if len(event_when) > 0:
                event_info.append(event_when[0]) #start time
                event_info.append(event_when[1]) #end time
            else:
                logging.debug(GoogCalendar.LOG_PREFIX + "No time information in event")
                event_info.append(None) #start time
                event_info.append(None) #end time
            
            logging.debug(GoogCalendar.LOG_PREFIX + "Event info: " + str(event_info))
            ret.append(tuple(event_info))
        
        return tuple(ret), len(ret)
    
    
    def InsertNewEvent(self, title, content, location,
                       start_time, end_time):
        """
        Inserts new event to calendar
        
        start_time and end_time are in ISO 8601 format.
        
        """
        ret = 0
        
        event = gdata.calendar.CalendarEventEntry()
        event.title = atom.Title(text=title)
        event.content = atom.Content(text=content)
        event.where.append(gdata.calendar.Where(value_string=location))
        
        event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
        
        new_event = self.gcal_service.InsertEvent(event,     
                                                '/calendar/feeds/default/private/full')
        """
        TODO: Proper error handling
        """
        
        return ret
        
               
    def _parse_event_time(self, raw_xml):
        """
        Parses event information from following xml structure:
        
        <?xml version='1.0' encoding='UTF-8'?>
        <ns0:when endTime="2008-03-16T19:00:00.000+02:00" startTime="2008-03-16T17:00:00.000+02:00" 
        xmlns:ns0="http://schemas.google.com/g/2005">
            <ns0:reminder method="alert" minutes="10" />
        </ns0:when>
        
        Returns a tuple constructed in following way (times are UNIX times):
            (start_time, end_time, reminder_method, reminder_alert_time)
        
        FIXME: This is very bad way of parsing event information. Find proper way of extracting
        information. Where is it in gdata?
        """
        
        ret = []
        
        """
        Some very basic sanity checks
        Time should be between xxx_time_pos1 and xxx_time_pos2
        """
        end_time_pos1 = raw_xml.find('endTime="')
        end_time_pos2 = raw_xml.find('" startTime')
        start_time_pos1 = raw_xml.find('startTime="')
        start_time_pos2 = raw_xml.find('" xmlns')
        
        if end_time_pos1 <> -1 and end_time_pos2 <> -1 and start_time_pos1 <> -1 and start_time_pos2 <> -1:
            
            end_time = raw_xml[end_time_pos1+len('endTime="'):end_time_pos2]
            start_time = raw_xml[start_time_pos1+len('startTime="'):start_time_pos2]
            
            
            reminder_method = raw_xml[raw_xml.find('method="')+len('method="'):raw_xml.find('" min')]
            reminder_alert_time = raw_xml[raw_xml.find('minutes="')+len('minutes="'):raw_xml.find('" />')]
            
            etime = datetime.datetime.fromtimestamp(xml.utils.iso8601.parse(end_time))                        
            stime = datetime.datetime.fromtimestamp(xml.utils.iso8601.parse(start_time))
            
            #Converting times to UNIX times
            ret = [time.mktime(stime.timetuple())+1e-6*stime.microsecond, 
                   time.mktime(etime.timetuple())+1e-6*etime.microsecond, 
                   reminder_method, reminder_alert_time]

        else:
            logging.critical(GoogCalendar.LOG_PREFIX + "Event xml returned by Google is not what was expected!")        
        
        return tuple(ret)
        